很多人都在学ggplot2
,也有很多「高手」,比如你搜索「精通ggplot2」,你甚至会看到各种「从入门到精通」的课程,然而都是各种照猫画老虎的入门菜谱而已,当然广大群众去上课也可能只是为了求菜谱好在需要的时候,可以copy-paste而已。
ggplot2
学习容易,因为无非是图层叠加,而图层的参数一致性非常好,网上也有很全的文档。然而要深入很难,比如你想干点什么是ggplot2
自身没有支持的,你需要自己去hack,需要用到更底层的东西的时候,一般情况下,会无从入手,尝试之后才知道什么叫绝望!因为ggplot2
套路实在是太深。
正如我在《听说你还不会画热图》吐槽的,有些所谓的「高手」实际上连ggplot2
底层是什么都不知道!然后可以把ggplot2
吹上天。很多人根本不知道当你打下ggplot
这个命令的时候,到底发生了什么?要理解ggplot2
并不容易,正如前面所说「套路太深」,没有金钢钻,读不了它的代码,有没有一个简单点的包,能够读懂代码,并且有助于理解ggplot2
呢?我想我的meme
包就非常好,虽然仿的有点肤浅,然而胜在简单,看过下面这两篇文章的,都知道这个包在做什么。
在非常清楚主要函数的目的之后,代码读起来,仿佛就有了指南一般,又因为包的功能简单,代码简短,容易理顺代码的含义。这个包除去注释和空白行,只有146行,你没有看错,就是这么短。功能简单、目的明确,但能读懂也不是很容易。
1. meme
的输出不是图?为什么能画出图?
meme <- function(img, upper="", lower="", size="auto", color="white", font="Impact",
vjust = .1, bgcolor="black", r = 0.2) {
x <- image_read(img)
info <- image_info(x)
imageGrob <- rasterGrob(x)
p <- structure(
list(img = img, imageGrob = imageGrob,
width = info$width, height = info$height,
upper=upper, lower=lower,
size = size, color = color,
font = font, vjust = vjust,
bgcolor = bgcolor, r = r),
class = c("meme", "recordedplot"))
p
}
如果你看代码,meme
的输出就是一个叫meme
的对象,只是个简单的list,而内容基本上就是参数而已。除了图已经读了(避免重复读图)。那么这样一个list
,为什么当你打meme
命令的时候会出图?
ggplot
的输出是一个叫gg
的对象,实质上也是一个简单的list,同理你就能理解为什么它能出图?
2. 为什么+
可以改变图的内容和状态?
在meme
包中,你可以用+aes()
或者+list()
来改变打印的字、字体、颜色等,为什么能改变内容和状态?相对应于ggplot2
,+geom_XXX
图层是改变内容,+theme()
是改变状态,这是如何实现的?
如果理解了1
,你就能理解2
这个问题。
3. 为什么ggsave
能识别meme
对象?
meme_save <- function(x, file, width = NULL, height = NULL, ...) {
if (!is(x, "meme")) {
stop("x should be an instance of 'meme'")
}
if (is.null(width) && is.null(height)) {
width <- px2in(x$width)
height <- px2in(x$height)
} else if (is.null(width)) {
width <- height / asp(x)
} else {
height <- width * asp(x)
}
ggsave(filename = file, plot = x,
width = width,
height = height,
...)
}
meme
包中提供的meme_save()
函数,无非是设置了原图片的长宽比,然后直接应用ggsave
来保存,为什么ggsave
能识别meme
?
如果ggsave
是原型函数,那么我在meme
里定义相应的方法就可以了,但你看ggplot2::ggsave
的代码,它只是一个普通的函数,但我们在使用cowplot
拼图的时候,输出也不是gg
或ggplot
对象了,已经和ggplot2
不兼容了,然而plot_grid
的输出,你也能用ggsave
来保存?ggsave
到底干了什么?它是如何识别ggplot
以外的对象的?
事实上如果你把base plot套在expression或formula里,我们甚至可以让ggsave
来支持base plot,使用诸如ggsave(expression(plot()))
或ggsave(~plot())
这样的语法,当然默认是不支持的,需要我们做点什么东西,如果你真正理解了,你就能够实现出来。
前面三个问题,从输入、中间操作和输出三个角度,拷问我们自己,如果能够回答,那么就算没有理解整个过程,起码是有点感性认识,毕竟ggplot2
的实现是复杂的,套路很深,但很多是细节性的,大的方向、思路无非就是支持图形语法来进行「中间操作」,改变最终出图的内容。好好思考一下这三个问题。
前面三个问题是核心的,再提问一点相关的。
4. 为什么使用传统的出图方式来画meme
,在循环中需要显示print(object)
?而ggsave
则不用?到底区别在那里?
这是ggplot2
一个非常常见的问题,借用meme
来思考一下,可以与问题3
联合想一下,ggsave
到底干了什么?为什么不循环可以?而循环就见了鬼了?
5. 为什么meme
对象能够被ggimage
和cowplot
识别?
这个问题有点跳跃了,ggimage
对meme
是原生支持,因为我是ggimage
包作者,我想支持就能支持,但到底我是怎么实现的?而cowplot
,不好意思,它是不支持的,而我不是作者,那么我就去看cowplot
的实现中,有没有什么可以hack的,我用「欺骗」的手段,穿个「马甲」让它来认,这都挺有意思的,到底怎么实现?